package priv.wangy.micro.common.dstb.autoId;

import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author zhangjq
 * @since 2023-10-16
 */
@Component
@Scope("prototype")
public class AutoIdGenerator {

    private final AutoIdProperties properties;

    private final AtomicInteger number;

    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    private final Map<Integer, Integer[]> map = new HashMap<>();

    private long time;

    public AutoIdGenerator(AutoIdConfig config) {
        this.properties = config.getAutoIdProperties();
        this.number = new AtomicInteger(this.properties.getInitial());
        this.time = System.currentTimeMillis();
    }

    public void sleep() {
        lock.writeLock().lock();
        long now = System.currentTimeMillis();
        if ((now / 1000) == (time / 1000)) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        lock.writeLock().unlock();
    }

    public void clear() {
        lock.writeLock().lock();
        number.set(this.properties.getInitial());
        lock.writeLock().unlock();
    }

    private int nextInt(int n) {
        lock.readLock().lock();
        Integer[] ds = map.get(n);
        if (Objects.isNull(ds)) {
            ds = new Integer[]{(int) Math.pow(10, n - 1), (int) Math.pow(10, n)};
            map.put(n, ds);
        }
        int x = number.get() % ds[0];
        if (x == 0) {
            lock.readLock().unlock();
            sleep();
            lock.readLock().lock();
        }
        if (x == 1) {
            this.time = System.currentTimeMillis();
        }
        if (number.get() >= ds[1]) {
            lock.readLock().unlock();
            clear();
            lock.readLock().lock();
        }
        int i = number.getAndIncrement();
        lock.readLock().unlock();
        return i;
    }

    public String simpleNext(int n) {
        return String.format("%0" + n + "d", nextInt(n));
    }

    public String next(int n) {
        return String.format("%s%0" + n + "d", DateFormatUtils.format(new Date(), "yyMMddHHmmss"), nextInt(n));
    }
}
